package com.netty.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.netty.model.entity.Msg;
import com.netty.model.enums.MsgActionEnum;
import com.netty.model.netty.ChatMsg;
import com.netty.model.netty.DataContent;
import com.netty.model.netty.UserChannelRel;
import com.netty.services.MsgService;
import com.netty.util.SpringUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    // 用于记录和管理所有客户端的channle
    public static ChannelGroup users = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {

        MsgService msgService = SpringUtil.getBean(MsgService.class);

        // 接受客户端传输过来的内容
        String content = textWebSocketFrame.text();
        // 获取通道
        Channel currentChannel = channelHandlerContext.channel();

        // 1，获取客户端发来的消息
        DataContent dataContent = JSONUtil.toBean(content, DataContent.class);

        // 2，判断消息类型，根据不同的类型来处理不同的业务
        if (dataContent.getAction() == MsgActionEnum.CONNECT.type) { // 第一次连接
            // --2.1，当websocket 第一次open的时候，初始化channel，把用户的channel和Userid关联起来
            Integer sendUserId = dataContent.getChatMsg().getSendUserId();
            UserChannelRel.put(String.valueOf(sendUserId), currentChannel);

            // 打印输出 连接的管道
            UserChannelRel.output();

        } else if (dataContent.getAction() == MsgActionEnum.CHAT.type) { // 发送消息
            // --2.2，聊天类型的消息，把聊天记录保存到数据库，同时表示消息的签收状态
            ChatMsg chatMsg = dataContent.getChatMsg();
            String msgTest = chatMsg.getMsg();
            Integer sendUserId = chatMsg.getSendUserId();
            Integer receiverUserId = chatMsg.getReceiverUserId();

            // 保存消息到数据库，并且标记为未读
            Msg msg = new Msg();
            msg.setSendUserId(sendUserId);
            msg.setReceiverUserId(receiverUserId);
            msg.setCreateTime(new Date());
            msg.setMsgFlag(0);
            msg.setIsRead(0);
            msg.setContent(msgTest);
            msgService.addMsg(msg);
            log.debug("保存消息到数据库，并且标记为未读");
            chatMsg.setMsgId(msg.getMsgId());

            // 发送消息
            // 从全局用户Channel映射关系中获取接收方的channel
            Channel receiverChannel = UserChannelRel.get(String.valueOf(receiverUserId));
            if (receiverChannel == null) {
                // TODO channel为空代表用户离线，推送消息
            } else {
                // 当receiverChannel不为空的时候，从ChannelGroup去查找对应的channel是否存在
                Channel findChannel = users.find(receiverChannel.id());
                if (findChannel != null) {
                    // 用户在线，发送消息
                    receiverChannel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(chatMsg)));
                } else {
                    // 用户离线 TODO 推送消息
                }
            }

        } else if (dataContent.getAction() == MsgActionEnum.SIGNED.type) { // 签收消息
            // --2.3，签收消息类型，针对具体的消息进行签收，修改数据库中对应的消息签收状态【已签收】
            // 扩展字段在signed类型的消息中，代表需要去签收的消息id，逗号间隔
            String msgIdsStr = dataContent.getExtand();
            if (StrUtil.isNotEmpty(msgIdsStr)){
                String[] split = msgIdsStr.split(",");

                List<Integer> msgIdList = new ArrayList<>();
                for (int i = 0; i < split.length; i++) {
                    String s = split[i];
                    if (StrUtil.isNotEmpty(s)) {
                        msgIdList.add(Integer.parseInt(s));
                    }
                }
                log.debug("msgIdList=" + msgIdList.toString());

                if (CollUtil.isNotEmpty(msgIdList)) {
                    // 批量签收
                    msgService.updateMsgToSignStatus(msgIdList);
                }
            }

        } else if (dataContent.getAction() == MsgActionEnum.KEEPALIVE.type) { // 心跳检测
            // --2.4，心跳类型的消息
            log.debug("收到来自channel为[" + currentChannel + "]的心跳包");
        }
    }

    /**
     * 当客户端连接服务端之后（打开连接）
     * 获取客户端的channle，并且放到ChannelGroup中去进行管理
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        users.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

        // 当触发handlerRemoved，ChannelGroup会自动移除duiying对应客户端的channel
        log.debug("客户端断开，channle对应的长id为" + ctx.channel().id().asLongText());
        log.debug("客户端断开，channle对应的短id为" + ctx.channel().id().asShortText());

        users.remove(ctx.channel());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 发生异常之后关闭连接（关闭channel），随后从ChannelGroup中移除
        ctx.channel().close();
        users.remove(ctx.channel());
    }
}
